/*
 * Copyright 2025 LinQingYing. and contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * The use of this source code is governed by the Apache License 2.0,
 * which allows users to freely use, modify, and distribute the code,
 * provided they adhere to the terms of the license.
 *
 * The software is provided "as-is", and the authors are not responsible for
 * any damages or issues arising from its use.
 *
 */

package org.cangnova.cangjie.ide.run


import com.intellij.execution.Executor
import com.intellij.execution.ExternalizablePath
import com.intellij.execution.configurations.*
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.application.Experiments
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.options.SettingsEditorGroup
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import org.cangnova.cangjie.ide.experiments.CjExperiments
import org.cangnova.cangjie.utils.isUnitTestMode
import org.cangnova.cangjie.utils.toPath
import org.jdom.Element
import java.nio.file.Path
import java.nio.file.Paths


abstract class CjCommandConfiguration(
    project: Project,
    name: String,
    factory: ConfigurationFactory
) : LocatableConfigurationBase<RunProfileState>(project, factory, name),
    RunConfigurationWithSuppressedDefaultDebugAction {

    companion object {
        val emulateTerminalDefault: Boolean
            get() = isFeatureEnabled(CjExperiments.EMULATE_TERMINAL) && !isUnitTestMode

        private const val DEBUG_ALL_NAME: String = "DebugAllEnabled"

        private const val RUN_AS_TEST_NAME: String = "RunAsTest"


        val DEBUG_ALL_KEY: Key<Boolean?> = Key.create<Boolean?>("DEBUG_ALL_TASKS")

        val RUN_AS_TEST_KEY: Key<Boolean?> = Key.create<Boolean?>("RUN_AS_TEST")

        val IS_TEST_TASK_RERUN_KEY: Key<Boolean?> = Key.create<Boolean?>("IS_TEST_TASK_RERUN")


    }

    class Default(project: Project, factory: ConfigurationFactory) : CjCommandConfiguration(
        project, "CangJie Default", factory
    ) {
        override var command: String
            get() = ""
            set(value) {}

        override fun getState(
            executor: Executor,
            environment: ExecutionEnvironment
        ): RunProfileState? {
            return null
        }

        override fun getConfigurationEditor(): SettingsEditor<out RunConfiguration?> {
//        创建
            val group = SettingsEditorGroup<RunConfiguration>()


            return group
        }
    }

    abstract var command: String
    var emulateTerminal: Boolean = emulateTerminalDefault

    var workingDirectory: Path? = if (!project.isDefault) {
        project.basePath?.toPath()
    } else {
        null
    }

    private var _isRunAsTest = false
    fun isRunAsTest(): Boolean {
        return _isRunAsTest
    }

    fun setRunAsTest(runAsTest: Boolean) {
        this._isRunAsTest = runAsTest
        putUserData<Boolean?>(
            RUN_AS_TEST_KEY,
            runAsTest
        )
        putUserData<Boolean?>(
            IS_TEST_TASK_RERUN_KEY,
            runAsTest
        )
    }

    override fun writeExternal(element: Element) {
        super.writeExternal(element)
        element.writeString("command", command)

        element.writePath("workingDirectory", workingDirectory)
        element.writeBool("emulateTerminal", emulateTerminal)

    }

    override fun readExternal(element: Element) {
        super.readExternal(element)

        element.readString("command")?.let { command = it }
        element.readPath("workingDirectory")?.let { workingDirectory = it }
        element.readBool("emulateTerminal")?.let { emulateTerminal = it }
    }
}

fun Element.writeString(name: String, value: String) {
    val opt = Element("option")
    opt.setAttribute("name", name)
    opt.setAttribute("value", value)
    addContent(opt)
}

fun Element.readString(name: String): String? =
    children
        .find { it.name == "option" && it.getAttributeValue("name") == name }
        ?.getAttributeValue("value")

fun Element.writePath(name: String, value: Path?) {
    if (value != null) {
        val s = ExternalizablePath.urlValue(value.toString())
        writeString(name, s)
    }
}

fun Element.readPath(name: String): Path? {
    return readString(name)?.let { Paths.get(ExternalizablePath.localPathValue(it)) }
}

fun Element.writeBool(name: String, value: Boolean) {
    writeString(name, value.toString())
}

fun Element.readBool(name: String): Boolean? =
    readString(name)?.toBoolean()

fun <E : Enum<*>> Element.writeEnum(name: String, value: E) {
    writeString(name, value.name)
}

inline fun <reified E : Enum<E>> Element.readEnum(name: String): E? {
    val variantName = readString(name) ?: return null
    return try {
        java.lang.Enum.valueOf(E::class.java, variantName)
    } catch (_: IllegalArgumentException) {
        null
    }
}

fun isFeatureEnabled(featureId: String): Boolean {
    // Hack to pass values of experimental features in headless IDE run
    // Should help to configure IDE-based tools like Qodana
//    if (isHeadlessEnvironment) {
//        val value = Stdlib.getProperty(featureId)?.toBooleanStrictOrNull()
//        if (value != null) return value
//    }

    return Experiments.getInstance().isFeatureEnabled(featureId)
}
